QuickOPC User's Guide and Reference
Processing generic data
Extensions > Integrated Extensions > OPC UA Complex Data Extension > Generic data and data types > Working with generic data > Processing generic data

In order to process generic data (a sub-type of GenericData Class), you need to first determine its kind (see Generic data kinds). You can do it by

All generic data classes share a common DataType Property, which contains an optional data type object.

After you have determined the kind of generic data you are dealing with, you can use the generic data object cast to appropriate type, and work with properties and methods that are specific to that data type kind. For example, SequenceData contains the elements of the sequence in its Elements Property.

The following example shows how generic data can be processed.

.NET

// Shows how to process generic data type, displaying some of its properties, recursively.

using System;
using System.Collections.Generic;
using OpcLabs.BaseLib.DataTypeModel;
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.ComplexData;
using OpcLabs.EasyOpc.UA.OperationModel;

namespace UADocExamples.ComplexData._GenericData
{
    class DataTypeKind1
    {
        public static void Main1()
        {
            // Define which server and node we will work with.
            UAEndpointDescriptor endpointDescriptor =
                "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer";
            // or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            // or "https://opcua.demo-this.com:51212/UA/SampleServer/"
            UANodeDescriptor nodeDescriptor =
                "nsu=http://test.org/UA/Data/ ;i=10239"; // [ObjectsFolder]/Data.Static.Scalar.StructureValue

            // Instantiate the client object.
            var client = new EasyUAClient();

            // Read a node. We know that this node returns complex data, so we can type cast to UAGenericObject.
            UAGenericObject genericObject;
            try
            {
                genericObject = (UAGenericObject)client.ReadValue(endpointDescriptor, nodeDescriptor);
            }
            catch (UAException uaException)
            {
                Console.WriteLine("*** Failure: {0}", uaException.GetBaseException().Message);
                return;
            }

            // Process the generic data type. We will inspect some of its properties, and dump them.
            ProcessGenericData(genericObject.GenericData, maximumDepth: 3);
        }
        

        // Process the generic data type. Its structure can sometimes be quite deep, therefore we are limiting the depth
        // of the recursion using maximumDepth.
        public static void ProcessGenericData(GenericData genericData, int maximumDepth)
        {
            if (maximumDepth == 0)
                return;

            Console.WriteLine();
            Console.WriteLine("genericData.DataType: {0}", genericData.DataType);

            switch (genericData.DataTypeKind)
            {
                case DataTypeKind.Enumeration:
                    Console.WriteLine("The generic data is an enumeration.");
                    var enumerationData = (EnumerationData) genericData;
                    Console.WriteLine("Its value is {0}.", enumerationData.Value);
                    // There is also a ValueName that you can inspect (if known).
                    break;

                case DataTypeKind.Opaque:
                    Console.WriteLine("The generic data is opaque.");
                    var opaqueData = (OpaqueData) genericData;
                    Console.WriteLine("Its size is {0} bits.", opaqueData.SizeInBits);
                    Console.WriteLine("The data bytes are {0}.", BitConverter.ToString(opaqueData.ByteArray));
                    // Use the Value property (a BitArray) if you need to access the value bit by bit.
                    break;

                case DataTypeKind.Primitive:
                    Console.WriteLine("The generic data is primitive.");
                    var primitiveData = (PrimitiveData) genericData;
                    Console.WriteLine("Its value is \"{0}\".", primitiveData.Value);
                    break;

                case DataTypeKind.Sequence:
                    Console.WriteLine("The generic data is a sequence.");
                    var sequenceData = (SequenceData) genericData;
                    Console.WriteLine("It has {0} elements.", sequenceData.Elements.Count);
                    Console.WriteLine("A dump of the elements follows.");
                    foreach (GenericData element in sequenceData.Elements)
                        ProcessGenericData(element, maximumDepth - 1);
                    break;

                case DataTypeKind.Structured:
                    Console.WriteLine("The generic data is structured.");
                    var structuredData = (StructuredData) genericData;
                    Console.WriteLine("It has {0} field data members.", structuredData.FieldData.Count);
                    Console.WriteLine("The names of the fields are: {0}.",
                        String.Join(", ", structuredData.FieldData.Keys));

                    Console.WriteLine("A dump of each of the fields follows.");
                    foreach (KeyValuePair<string, GenericData> pair in structuredData.FieldData)
                    {
                        Console.WriteLine();
                        Console.WriteLine("Field name: {0}", pair.Key);
                        ProcessGenericData(pair.Value, maximumDepth - 1);
                    }
                    break;

                case DataTypeKind.Union:
                    Console.WriteLine("The generic data is a union.");
                    var unionData = (UnionData)genericData;
                    Console.WriteLine("The name of current field is: {0}", unionData.FieldName);
                    Console.WriteLine("Current field value is: {0}", unionData.FieldValue);
                    break;
            }
        }
    }
}
# Shows how to process generic data type, displaying some of its properties, recursively.

# The QuickOPC package is needed. Install it using "pip install opclabs_quickopc".
import opclabs_quickopc

# Import .NET namespaces.
from System import *
from OpcLabs.BaseLib.DataTypeModel import *
from OpcLabs.EasyOpc.UA import *
from OpcLabs.EasyOpc.UA.OperationModel import *


def processGenericData(genericData, maximumDepth):
    if maximumDepth == 0:
        print('* Reached maximum depth *')
        return

    print()
    print('genericData.DataType: ', genericData.DataType, sep='')

    dataTypeKind = genericData.DataTypeKind
    if dataTypeKind == DataTypeKind.Enumeration:
        print('The generic data is an enumeration.')
        enumerationData = genericData
        print('Its value is ', enumerationData.Value, '.', sep='')
        # There is also a ValueName that you can inspect (if known).

    elif dataTypeKind == DataTypeKind.Opaque:
        print('The generic data is opaque.')
        opaqueData = genericData
        print('Its size is ', opaqueData.SizeInBits, ' bits.', sep='')
        print('The data bytes are ', BitConverter.ToString(opaqueData.ByteArray), '.', sep='')
        # Use the Value property (a BitArray) if you need to access the value bit by bit.

    elif dataTypeKind == DataTypeKind.Primitive:
        print('The generic data is primitive.')
        primitiveData = genericData
        print('Its value is "', primitiveData.Value, '".', sep='')

    elif dataTypeKind == DataTypeKind.Sequence:
        print('The generic data is a sequence.')
        sequenceData = genericData
        print('It has ', sequenceData.Elements.Count, ' elements.', sep='')
        print('A dump of the elements follows.')
        for element in sequenceData.Elements:
            processGenericData(element, maximumDepth - 1)

    elif dataTypeKind == DataTypeKind.Structured:
        print('The generic data is structured.')
        structuredData = genericData
        print('It has ', structuredData.FieldData.Count, ' field data members.', sep='')
        print('The names of the fields are: ', end='')
        for i, name in enumerate(structuredData.FieldData.Keys):
            if i != 0:
                print(', ', end='')
            print(name, end='')
        print('.')

        print('A dump of each of the fields follows.')
        for pair in structuredData.FieldData:
            print()
            print('Field name: ', pair.Key, sep='')
            processGenericData(pair.Value, maximumDepth - 1)

    elif dataTypeKind == DataTypeKind.Union:
        print('The generic data is a union.')
        unionData = genericData
        print('The name of current field is: ', unionData.FieldName, sep='')
        print('Current field value is: ', unionData.FieldValue, end='')


# Define which server and node we will work with.
endpointDescriptor = UAEndpointDescriptor('opc.tcp://opcua.demo-this.com:51210/UA/SampleServer')
# or 'http://opcua.demo-this.com:51211/UA/SampleServer' (currently not supported)
# or 'https://opcua.demo-this.com:51212/UA/SampleServer/'

# [ObjectsFolder]/Data.Static.Scalar.StructureValue
nodeDescriptor = UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=10239')

# Instantiate the client object.
client = EasyUAClient()

# Read a node. We know that this node returns complex data, so we can type cast to UAGenericObject.
try:
    print('Reading...')
    genericObject = IEasyUAClientExtension.ReadValue(client, endpointDescriptor, nodeDescriptor)
except UAException as uaException:
    print('*** Failure: ' + uaException.GetBaseException().Message)
    exit()
print('Reading successful.')

# Process the generic data type. We will inspect some of its properties, and dump them.
processGenericData(genericObject.GenericData, 3)

print()
print('Finished.')
' Shows how to process generic data type, displaying some of its properties, recursively.

Imports OpcLabs.BaseLib.DataTypeModel
Imports OpcLabs.EasyOpc.UA
Imports OpcLabs.EasyOpc.UA.ComplexData
Imports OpcLabs.EasyOpc.UA.OperationModel

Namespace ComplexData._GenericData

    Friend Class DataTypeKind1

        Public Shared Sub Main1()

            ' Define which server we will work with.
            Dim endpointDescriptor As UAEndpointDescriptor =
                    "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"
            ' or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            ' or "https://opcua.demo-this.com:51212/UA/SampleServer/"

            ' Define which node we will work with.
            Dim nodeDescriptor As UANodeDescriptor = _
                "nsu=http://test.org/UA/Data/ ;i=10239"  ' [ObjectsFolder]/Data.Static.Scalar.StructureValue

            ' Instantiate the client object.
            Dim client = New EasyUAClient

            ' Read a node. We know that this node returns complex data, so we can type cast to UAGenericObject.
            Dim genericObject As UAGenericObject
            Try
                genericObject = CType(client.ReadValue(endpointDescriptor, nodeDescriptor), UAGenericObject)
            Catch uaException As UAException
                Console.WriteLine("*** Failure: {0}", uaException.GetBaseException.Message)
                Exit Sub
            End Try

            ' Process the generic data type. We will inspect some of its properties, and dump them.
            ProcessGenericData(genericObject.GenericData, maximumDepth:=2)
        End Sub


        ' Process the generic data type. Its structure can sometimes be quite deep, therefore we are limiting the depth
        ' of the recursion using maximumDepth.
        Public Shared Sub ProcessGenericData(ByVal genericData As GenericData, ByVal maximumDepth As Integer)
            If (maximumDepth = 0) Then
                Return
            End If

            Console.WriteLine()
            Console.WriteLine("genericData.DataType: {0}", genericData.DataType)

            Select Case (genericData.DataTypeKind)
                Case DataTypeKind.Enumeration
                    Console.WriteLine("The generic data is an enumeration.")
                    Dim enumerationData = CType(genericData, EnumerationData)
                    Console.WriteLine("Its value is {0}.", enumerationData.Value)
                    ' There is also a ValueName that you can inspect (if known).

                Case DataTypeKind.Opaque
                    Console.WriteLine("The generic data is opaque.")
                    Dim opaqueData = CType(genericData, OpaqueData)
                    Console.WriteLine("Its size is {0} bits.", opaqueData.SizeInBits)
                    Console.WriteLine("The data bytes are {0}.", BitConverter.ToString(opaqueData.ByteArray))
                    ' Use the Value property (a BitArray) if you need to access the value bit by bit.

                Case DataTypeKind.Primitive
                    Console.WriteLine("The generic data is primitive.")
                    Dim primitiveData = CType(genericData, PrimitiveData)
                    Console.WriteLine("Its value is ""{0}"".", primitiveData.Value)

                Case DataTypeKind.Sequence
                    Console.WriteLine("The generic data is a sequence.")
                    Dim sequenceData = CType(genericData, SequenceData)
                    Console.WriteLine("It has {0} elements.", sequenceData.Elements.Count)
                    Console.WriteLine("A dump of the elements follows.")
                    For Each element As GenericData In sequenceData.Elements
                        ProcessGenericData(element, (maximumDepth - 1))
                    Next

                Case DataTypeKind.Structured
                    Console.WriteLine("The generic data is structured.")
                    Dim structuredData = CType(genericData, StructuredData)
                    Console.WriteLine("It has {0} field data members.", structuredData.FieldData.Count)
                    Console.WriteLine("The names of the fields are: {0}.", String.Join(", ", structuredData.FieldData.Keys))

                    Console.WriteLine("A dump of each of the fields follows.")
                    For Each pair As KeyValuePair(Of String, GenericData) In structuredData.FieldData
                        Console.WriteLine()
                        Console.WriteLine("Field name: {0}", pair.Key)
                        ProcessGenericData(pair.Value, (maximumDepth - 1))
                    Next

                Case DataTypeKind.Union
                    Console.WriteLine("The generic data is union.")
                    Dim unionData = CType(genericData, UnionData)
                    Console.WriteLine("The name of current field is: {0}", unionData.FieldName)
                    Console.WriteLine("Current field value is: {0}", unionData.FieldValue)
            End Select

        End Sub
    End Class
End Namespace

COM

// Shows how to process generic data type, displaying some of its properties, recursively

class procedure DataTypeKind1.Main;
var
  Client: _EasyUAClient;
  EndpointDescriptor: string;
  GenericObject: _UAGenericObject;
  NodeDescriptor: string;
begin
  EndpointDescriptor := 
    //'http://opcua.demo-this.com:51211/UA/SampleServer';
    //'https://opcua.demo-this.com:51212/UA/SampleServer/';
    'opc.tcp://opcua.demo-this.com:51210/UA/SampleServer';
  NodeDescriptor := 'nsu=http://test.org/UA/Data/ ;i=10239';  // [ObjectsFolder]/Data.Static.Scalar.StructureValue

  // Instantiate the client object
  Client := CoEasyUAClient.Create;

  // Read a node. We know that this node returns complex data, so we can type cast to UAGenericObject.

  try
    GenericObject := _UAGenericObject(IUnknown(Client.ReadValue(EndpointDescriptor, NodeDescriptor)));
  except
    on E: EOleException do
    begin
      WriteLn(Format('*** Failure: %s', [E.GetBaseException.Message]));
      Exit;
    end;
  end;

  // Process the generic data type. We will inspect some of its properties, and dump them.
  ProcessGenericData(GenericObject.GenericData, 2);
end;

Function VariantToBytes(Const Value: OleVariant): TBytes;
Var
  Size: Integer;
  pData: Pointer;
Begin
  Size := Succ(VarArrayHighBound(Value, 1) - VarArrayLowBound(Value, 1));
  SetLength(Result, Size);
  pData := VarArrayLock(Value);
  Try
    Move(pData^, Pointer(Result)^, Size);
  Finally
    VarArrayUnlock(Value);
  End;
End;

class procedure DatatypeKind1.ProcessGenericData(GenericData: OpcLabs_BaseLib_TLB._GenericData; MaximumDepth: Cardinal);
var
  ByteArray: OleVariant;
  Count: Cardinal;
  Element: OleVariant;
  ElementEnumerator: IEnumVARIANT;
  EnumerationData: _EnumerationData;
  First: boolean;
  Keys: string;
  OpaqueData: _OpaqueData;
  PrimitiveData: _PrimitiveData;
  SequenceData: _SequenceData;
  StructuredData: _StructuredData;
  Value: OpcLabs_BaseLib_TLB._GenericData;
begin
  if MaximumDepth = 0 then
    Exit;

  WriteLn;
  WriteLn('genericData.DataType: ', GenericData.DataType.ToString);

  case GenericData.DataTypeKind of
    DataTypeKind_Enumeration:
      begin
        WriteLn('The generic data is an enumeration.');
        EnumerationData := GenericData as _EnumerationData;
        WriteLn(Format('Its value is %s.', [EnumerationData.Value.ToString]));
        // There is also a ValueName that you can inspect (if known).
      end;
    DataTypeKind_Opaque:
      begin
        WriteLn('The generic data is opaque.');
        OpaqueData := GenericData as _OpaqueData;
        WriteLn(Format('Its size is %d bits.', [OpaqueData.SizeInBits]));
        TVarData(ByteArray).VType := varArray;
        TVarData(ByteArray).VArray := PVarArray(OpaqueData.ByteArray);
        WriteLn(Format('The data bytes are %s.', [TEncoding.ANSI.GetString(VariantToBytes(ByteArray))]));
        // Use the Value property (a BitArray) if you need to access the value bit by bit.
      end;
    DataTypeKind_Primitive:
      begin
        WriteLn('The generic data is primitive.');
        PrimitiveData := GenericData as _PrimitiveData;
        WriteLn(Format('Its value is "%s".', [PrimitiveData.Value]));
      end;
    DataTypeKind_Sequence:
      begin
        WriteLn('The generic data is a sequence.');
        SequenceData := GenericData as _SequenceData;
        WriteLn(Format('It has %s elements.', [SequenceData.Elements.Count.ToString]));
        WriteLn('A dump of the elements follows.');
        ElementEnumerator := SequenceData.Elements.GetEnumerator;
        while (ElementEnumerator.Next(1, Element, Count) = S_OK) do
        begin
          ProcessGenericData(IUnknown(Element) as OpcLabs_BaseLib_TLB._GenericData, MaximumDepth - 1);
        end;
      end;
    DataTypeKind_Structured:
      begin
        WriteLn('The generic data is structured.');
        StructuredData := GenericData as _StructuredData;
        WriteLn(Format('It has %s field data members.', [StructuredData.FieldData.Count.ToString]));
        ElementEnumerator := StructuredData.FieldData.GetEnumerator;
        Keys := '';
        First := True;
        while (ElementEnumerator.Next(1, Element, Count) = S_OK) do
        begin
          if First then
            First := False
          else
            Keys := Keys + ', ';
          Keys := Keys + Element.Key;
        end;
        WriteLn(Format('The names of the fields are: %s.', [Keys]));

        WriteLn('A dump of each of the fields follows.');
        ElementEnumerator := StructuredData.FieldData.GetEnumerator;
        while (ElementEnumerator.Next(1, Element, Count) = S_OK) do
        begin
          WriteLn;
          WriteLn(Format('Field name: %s', [Element.Key]));
          Value := IUnknown(Element.Value) as OpcLabs_BaseLib_TLB._GenericData;
          ProcessGenericData(Value, MaximumDepth - 1);
        end;
      end;
  end;

end;

 

 

In many cases, you will not be processing the generic data alone, but in parallel with the data type. See Processing data types for details. 

See Also

Reference